//---------------------------------------------------------------------
//  This file is part of the Background Motion solution.
// 
//  Copyright (C) Mindscape (TM).  All rights reserved.
//  http://www.mindscape.co.nz
// 
//  THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//---------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Reflection;

namespace Mindscape.BackgroundMotion.Core.Utilities
{
  /// <summary>
  /// Provides a simple reflection based mapper to perform a Data Mapping between two types of objects
  /// </summary>
  /// <remarks>
  /// We are working on the convention that the source object will be property based and the target will be field based
  /// This is used between our domain entities and DTO's
  /// </remarks>
  /// <typeparam name="T"></typeparam>
  public class DataMapper<T> where T : new()
  {
    // TODO: Not thread-safe - using ThreadStatic should be ok here

    private static Dictionary<Type, Dictionary<string, PropertyInfo>> _propertyReflectionCache =
      new Dictionary<Type, Dictionary<string, PropertyInfo>>();

    private static Dictionary<Type, FieldInfo[]> _fieldReflectionCache = new Dictionary<Type, FieldInfo[]>();

    /// <summary>
    /// Use a reflection based mapping to map between properties and fields
    /// </summary>
    public static T Map(object source)
    {
      T target = new T();
      FieldInfo[] fields = GetFieldSetInfo(typeof(T));

      if (source == null)
      {
        return target;
      }

      foreach (FieldInfo field in fields)
      {
        try
        {
          PropertyInfo sourceProperty = GetPropertyInfo(source.GetType(), field.Name);
          if (sourceProperty == null)
          {
            continue;
          }

          field.SetValue(target, sourceProperty.GetValue(source, null));
        }
        catch (InvalidCastException)
        {
        }
      }

      return target;
    }

    /// <summary>
    /// Finds a property in the reflection cache
    /// </summary>
    private static PropertyInfo GetPropertyInfo(Type t, string name)
    {
      if (_propertyReflectionCache.ContainsKey(t))
      {
        if (_propertyReflectionCache[t].ContainsKey(name))
        {
          return _propertyReflectionCache[t][name];
        }

        _propertyReflectionCache[t].Add(name, t.GetProperty(name, BindingFlags.Public | BindingFlags.Instance));
        return _propertyReflectionCache[t][name];
      }

      Dictionary<string, PropertyInfo> dictionary = new Dictionary<string, PropertyInfo>();
      _propertyReflectionCache.Add(t, dictionary);
      dictionary.Add(name, t.GetProperty(name, BindingFlags.Public | BindingFlags.Instance));

      return _propertyReflectionCache[t][name];
    }

    /// <summary>
    /// Finds a collection of field information from the reflection cache
    /// </summary>
    private static FieldInfo[] GetFieldSetInfo(Type t)
    {
      if (_fieldReflectionCache.ContainsKey(t))
      {
        return _fieldReflectionCache[t];
      }

      _fieldReflectionCache.Add(t, t.GetFields(BindingFlags.Public | BindingFlags.Instance));

      return _fieldReflectionCache[t];
    }
  }
}